%{
/*
 * Copyright 2013 Google Inc.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */
/*
 * Author: Author: ncardwell@google.com (Neal Cardwell)
 *
 * This is the parser for the packetdrill script language. It is
 * processed by the bison parser generator.
 *
 * For full documentation see: http://www.gnu.org/software/bison/manual/
 *
 * Here is a quick and dirty tutorial on bison:
 *
 * A bison parser specification is basically a BNF grammar for the
 * language you are parsing. Each rule specifies a nonterminal symbol
 * on the left-hand side and a sequence of terminal symbols (lexical
 * tokens) and or nonterminal symbols on the right-hand side that can
 * "reduce" to the symbol on the left hand side. When the parser sees
 * the sequence of symbols on the right where it "wants" to see a
 * nonterminal on the left, the rule fires, executing the semantic
 * action code in curly {} braces as it reduces the right hand side to
 * the left hand side.
 *
 * The semantic action code for a rule produces an output, which it
 * can reference using the $$ token. The set of possible types
 * returned in output expressions is given in the %union section of
 * the .y file. The specific type of the output for a terminal or
 * nonterminal symbol (corresponding to a field in the %union) is
 * given by the %type directive in the .y file. The action code can
 * access the outputs of the symbols on the right hand side by using
 * the notation $1 for the first symbol, $2 for the second symbol, and
 * so on.
 *
 * The lexer (generated by flex from lexer.l) feeds a stream of
 * terminal symbols up to this parser. Parser semantic actions can
 * access the lexer output for a terminal symbol with the same
 * notation they use for nonterminals.
 *
 * Here's an example rule with its semantic action in {} braces:
 *
 *     tcp_option
 *     ...
 *      | MSS INTEGER   {
 *        $$ = tcp_option_new(...);
 *        ...
 *        $$->data.mss.bytes = htons($2);
 *      }
 *
 * This rule basically says:
 *
 *   When the parser wants to see a tcp_option, if it sees an MSS from
 *   the lexer followed by an INTEGER from the lexer then run the
 *   action code that (a) stores in the output $$ a pointer to a
 *   struct tcp_option object, and then (b) stores in that object the
 *   value of the INTEGER token (accessed with $2).
 *
 */

/* The first part of the .y file consists of C code that bison copies
 * directly into the top of the .c file it generates.
 */

#include "types.h"

#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "gre_packet.h"
#include "ip.h"
#include "ip_packet.h"
#include "icmp_packet.h"
#include "logging.h"
#include "mpls.h"
#include "mpls_packet.h"
#include "tcp_packet.h"
#include "udp_packet.h"
#include "parse.h"
#include "script.h"
#include "tcp.h"
#include "tcp_options.h"

/* This include of the bison-generated .h file must go last so that we
 * can first include all of the declarations on which it depends.
 */
#include "parser.h"

/* Change this YYDEBUG to 1 to get verbose debug output for parsing: */
#define YYDEBUG 0
#if YYDEBUG
extern int yydebug;
#endif

extern FILE *yyin;
extern int yylineno;
extern char *yytext;
extern int yylex(void);
extern int yyparse(void);
extern int yywrap(void);
extern const char *cleanup_cmd;

/* This mutex guards all parser global variables declared in this file. */
pthread_mutex_t parser_mutex = PTHREAD_MUTEX_INITIALIZER;

/* The input to the parser: the path name of the script file to parse. */
static const char* current_script_path = NULL;

/* The starting line number of the input script statement that we're
 * currently parsing. This may be different than yylineno if bison had
 * to look ahead and lexically scan a token on the following line to
 * decide that the current statement is done.
 */
static int current_script_line = -1;

/*
 * We uses this object to look up configuration info needed during
 * parsing (such as whether packets are IPv4 or IPv6).
 */
struct config *in_config = NULL;

/* The output of the parser: an output script containing
 * 1) a linked list of options
 * 2) a linked list of events
 */
static struct script *out_script = NULL;

/* The test invocation to pass back to parse_and_finalize_config(). */
struct invocation *invocation;

/* Copy the script contents into our single linear buffer. */
void copy_script(const char *script_buffer, struct script *script)
{
	DEBUGP("copy_script\n");

	free(script->buffer);
	script->length = strlen(script_buffer);
	script->buffer = strdup(script_buffer);
	assert(script->buffer != NULL);

	DEBUGP("copy_script: %d bytes\n", script->length);
}

/* Read the script file into a single linear buffer. */
void read_script(const char *script_path, struct script *script)
{
	int size = 0;

	DEBUGP("read_script(%s)\n", script_path);

	while (script->buffer == NULL) {
		struct stat script_info;
		int fd = -1;

		/* Allocate a buffer big enough for the whole file. */
		if (stat(script_path, &script_info) != 0)
			die("parse error: stat() of script file '%s': %s\n",
			    script_path, strerror(errno));

		/* Pick a buffer size larger than the file, so we'll
		 * know if the file grew.
		 */
		size = max((int)script_info.st_size, size) + 1;

		script->buffer = malloc(size);
		assert(script->buffer != NULL);

		/* Read the file into our buffer. */
		fd = open(script_path, O_RDONLY);
		if (fd < 0)
			die("parse error opening script file '%s': %s\n",
			    script_path, strerror(errno));

		script->length = read(fd, script->buffer, size);
		if (script->length < 0)
			die("parse error reading script file '%s': %s\n",
			    script_path, strerror(errno));

		/* If we filled the buffer, then probably another
		 * process wrote more to the file since our stat call,
		 * so we should try again.
		 */
		if (script->length == size) {
			free(script->buffer);
			script->buffer = NULL;
			script->length = 0;
		}

		if (close(fd))
			die_perror("close");
	}
	DEBUGP("read_script: %d bytes\n", script->length);
}


/* The public entry point for the script parser. Parses the
 * text script file with the given path name and fills in the script
 * object with the parsed representation.
 */
int parse_script(struct config *config,
		 struct script *script,
		 struct invocation *callback_invocation)
{
	/* This bison-generated parser is not multi-thread safe, so we
	 * have a lock to prevent more than one thread using the
	 * parser at the same time. This is useful in the wire server
	 * context, where in general we may have more than one test
	 * thread running at the same time.
	 */
	if (pthread_mutex_lock(&parser_mutex) != 0)
		die_perror("pthread_mutex_lock");

#if YYDEBUG
	yydebug = 1;
#endif

	/* Now parse the script from our buffer. */
	yyin = fmemopen(script->buffer, script->length, "r");
	if (yyin == NULL)
		die_perror("fmemopen: parse error opening script buffer");

	current_script_path = config->script_path;
	in_config = config;
	out_script = script;
	invocation = callback_invocation;

	/* We have to reset the line number here since the wire server
	 * can do more than one yyparse().
	 */
	yylineno = 1;

	int result = yyparse();		/* invoke bison-generated parser */
	current_script_path = NULL;

	if (fclose(yyin))
		die_perror("fclose: error closing script buffer");

	/* Unlock parser. */
	if (pthread_mutex_unlock(&parser_mutex) != 0)
		die_perror("pthread_mutex_unlock");

	return result ? STATUS_ERR : STATUS_OK;
}

/* Bison emits code to call this method when there's a parse-time error.
 * We print the line number and the error message.
 */
static void yyerror(const char *message)
{
	fprintf(stderr, "%s:%d: parse error at '%s': %s\n",
		current_script_path, yylineno, yytext, message);
}

/* After we finish parsing each line of a script, we analyze the
 * semantics of the line. If we encounter an error then we print the
 * error message to stderr and exit with an error.
 */
static void semantic_error(const char* message)
{
	assert(current_script_line >= 0);
	die("%s:%d: semantic error: %s\n",
	    current_script_path, current_script_line, message);
}

/* This standard callback is invoked by flex when it encounters
 * the end of a file. We return 1 to tell flex to return EOF.
 */
int yywrap(void)
{
	return 1;
}

/* Create and initalize a new expression. */
static struct expression *new_expression(enum expression_t type)
{
	struct expression *expression = calloc(1, sizeof(struct expression));
	expression->type = type;
	return expression;
}

/* Create and initalize a new integer expression with the given
 * literal value and format string.
 */
static struct expression *new_integer_expression(s64 num, const char *format)
{
	struct expression *expression = new_expression(EXPR_INTEGER);
	expression->value.num = num;
	expression->format = format;
	return expression;
}

/* Create and initalize a new one-element expression_list. */
static struct expression_list *new_expression_list(
	struct expression *expression)
{
	struct expression_list *list;
	list = calloc(1, sizeof(struct expression_list));
	list->expression = expression;
	list->next = NULL;
	return list;
}

/* Add the expression to the end of the list. */
static void expression_list_append(struct expression_list *list,
				   struct expression *expression)
{
	while (list->next != NULL) {
		list = list->next;
	}
	list->next = new_expression_list(expression);
}

/* Create and initialize a new option. */
static struct option_list *new_option(char *name, char *value)
{
	struct option_list *opt = calloc(1, sizeof(struct option_list));
	opt->name = name;
	opt->value = value;
	return opt;
}

/* Create and initialize a new event. */
static struct event *new_event(enum event_t type)
{
	struct event *e = calloc(1, sizeof(struct event));
	e->type = type;
	e->time_usecs_end = NO_TIME_RANGE;
	e->offset_usecs = NO_TIME_RANGE;
	return e;
}

static int parse_hex_byte(const char *hex, u8 *byte)
{
	if (!isxdigit((int)hex[0]) || !isxdigit((int)hex[1])) {
		return STATUS_ERR;	/* need two hex digits per byte */
	}
	char buf[] = { hex[0], hex[1], '\0' };
	char* buf_end = NULL;
	u32 byte_value = strtoul(buf, &buf_end, 16);
	assert(byte_value <= 0xff);
	assert(buf_end == buf + 2);
	*byte = byte_value;
	return STATUS_OK;
}

/* Converts a hex string in 'hex' into bytes and stores them in a
 * buffer 'buf' of length 'buf_len' bytes; returns number of bytes in
 * out_len. Works for hex strings of arbitrary size, such as very long
 * TCP Fast Open cookies.
 */
static int parse_hex_string(const char *hex, u8 *buf, int buf_len,
			    int *out_len)
{
	u8 *out = buf;
	u8 *buf_end = buf + buf_len;
	while (hex[0] != '\0') {
		if (out >= buf_end) {
			return STATUS_ERR;	/* ran out of output space */
		}
		if (parse_hex_byte(hex, out))
			return STATUS_ERR;	/* bad character */
		hex += 2;
		out += 1;
	}
	*out_len = out - buf;
	assert(*out_len <= buf_len);
	return STATUS_OK;
}

static struct tcp_option *new_tcp_fast_open_option(const char *cookie_string,
						   char **error, bool exp)
{
	int cookie_string_len = strlen(cookie_string);
	if (cookie_string_len & 1) {
		asprintf(error,
			 "TCP fast open cookie has an odd number of digits");
		return NULL;
	}
	int cookie_bytes = cookie_string_len / 2;  /* 2 hex chars per byte */
	int max_bytes = exp ? MAX_TCP_FAST_OPEN_EXP_COOKIE_BYTES :
			      MAX_TCP_FAST_OPEN_COOKIE_BYTES;
	if (cookie_bytes > max_bytes) {
		asprintf(error, "TCP fast open cookie too long");
		asprintf(error, "TCP fast open cookie of %d bytes "
			 "exceeds maximum cookie length of %d bytes",
			 cookie_bytes, max_bytes);
		return NULL;
	}
	u8 option_bytes = cookie_bytes + (exp ? TCPOLEN_EXP_FASTOPEN_BASE :
	                                        TCPOLEN_FASTOPEN_BASE);
	struct tcp_option *option;
	option = tcp_option_new(exp ? TCPOPT_EXP : TCPOPT_FASTOPEN,
				option_bytes);
	if (exp)
		option->data.fast_open_exp.magic = htons(TCPOPT_FASTOPEN_MAGIC);

	int parsed_bytes = 0;
	/* Parse cookie. This should be an ASCII hex string
	 * representing an even number of bytes (4-16 bytes). But we
	 * do not enforce this, since we want to allow test cases that
	 * supply invalid cookies.
	 */
	if (parse_hex_string(cookie_string,
			     exp ? option->data.fast_open_exp.cookie :
				   option->data.fast_open.cookie,
			     exp ? sizeof(option->data.fast_open_exp.cookie):
			           sizeof(option->data.fast_open.cookie),
			     &parsed_bytes)) {
		free(option);
		asprintf(error,
			 "TCP fast open cookie '%s' is not a valid hex string",
			cookie_string);
		return NULL;
	}
	assert(parsed_bytes == cookie_bytes);
	return option;
}

static struct tcp_option *new_md5_option(const char *digest_string,
					 char **error)
{
	struct tcp_option *option;
	int digest_string_len = strlen(digest_string);
	int digest_bytes = digest_string_len / 2;
	int parsed_bytes = 0;

	if (digest_bytes > TCP_MD5_DIGEST_LEN) {
		asprintf(error, "TCP MD5 digest longer than 16 bytes");
		return NULL;
	}

	option = tcp_option_new(TCPOPT_MD5SIG, TCPOLEN_MD5_BASE + digest_bytes);

	/* Parse MD5 digest. This should be an ASCII hex string representing 16
	 * bytes. But we allow smaller buffers, since we want to allow test
	 * cases that supply invalid cookies.
	 */
	if (parse_hex_string(digest_string,
			     option->data.md5.digest,
			     sizeof(option->data.md5.digest),
			     &parsed_bytes)) {
		free(option);
		asprintf(error, "TCP MD5 digest is not a valid hex string");
		return NULL;
	}
	assert(parsed_bytes <= digest_bytes);
	return option;
}

static struct packet *append_gre(struct packet *packet, struct expression *expr)
{
	struct gre *gre = &expr->value.gre;
	char *error = NULL;
	if (gre_header_append(packet, gre, &error))
		semantic_error(error);
	free(expr);
	return packet;
}

%}

%locations
%expect 3  /* we expect shift/reduce conflicts */
/* The %union section specifies the set of possible types for values
 * for all nonterminal and terminal symbols in the grammar.
 */
%union {
	s64 integer;
	double floating;
	char *string;
	char *reserved;
	s64 time_usecs;
	enum direction_t direction;
	enum ip_ecn_t ip_ecn;
	struct tos_spec tos_spec;
	struct ip_info ip_info;
	struct mpls_stack *mpls_stack;
	struct mpls mpls_stack_entry;
	u16 port;
	s32 window;
	u16 urg_ptr;
	u32 sequence_number;
	struct {
		int protocol;		/* IPPROTO_TCP or IPPROTO_UDP */
		u32 start_sequence;
		u16 payload_bytes;
	} tcp_sequence_info;
	struct option_list *option;
	struct event *event;
	struct packet *packet;
	struct syscall_spec *syscall;
	struct command_spec *command;
	struct code_spec *code;
	struct tcp_option *tcp_option;
	struct tcp_options *tcp_options;
	struct expression *expression;
	struct expression_list *expression_list;
	struct errno_spec *errno_info;
	struct {
		u16 src_port;
		u16 dst_port;
	} port_info;
}

/* The specific type of the output for a symbol is given by the %type
 * directive. By convention terminal symbols returned from the lexer
 * have ALL_CAPS names, and nonterminal symbols have lower_case names.
 */
%token ELLIPSIS
%token <reserved> SA_FAMILY SIN_PORT SIN_ADDR _HTONS_ INET_ADDR INET6_ADDR
%token <reserved> MSG_NAME MSG_IOV MSG_FLAGS MSG_CONTROL
%token <reserved> CMSG_LEVEL CMSG_TYPE CMSG_DATA
%token <reserved> FD EVENTS REVENTS ONOFF LINGER
%token <reserved> U32 U64 PTR
%token <reserved> ACK ECR EOL MSS NOP SACK SACKOK TIMESTAMP VAL WIN WSCALE
%token <reserved> URG MD5 FAST_OPEN FAST_OPEN_EXP
%token <reserved> TOS FLAGS FLOWLABEL
%token <reserved> ECT0 ECT1 CE ECT01 NO_ECN
%token <reserved> IPV4 IPV6 ICMP UDP RAW GRE MTU ID
%token <reserved> MPLS LABEL TC TTL
%token <reserved> OPTION
%token <reserved> SUM OFF KEY SEQ
%token <reserved> NONE CHECKSUM SEQUENCE PRESENT
%token <reserved> EE_ERRNO EE_CODE EE_DATA EE_INFO EE_ORIGIN EE_TYPE
%token <reserved> SCM_SEC SCM_NSEC
%token <floating> FLOAT
%token <integer> INTEGER HEX_INTEGER
%token <string> WORD STRING BACK_QUOTED CODE IPV4_ADDR IPV6_ADDR
%type <direction> direction
%type <ip_info> ip_info opt_ip_info
%type <tos_spec> tos_spec
%type <ip_ecn> ip_ecn
%type <option> option options opt_options
%type <event> event events event_time action
%type <time_usecs> time opt_end_time
%type <packet> packet_spec tcp_packet_spec udp_packet_spec icmp_packet_spec
%type <packet> packet_prefix
%type <syscall> syscall_spec
%type <command> command_spec
%type <code> code_spec
%type <mpls_stack> mpls_stack
%type <mpls_stack_entry> mpls_stack_entry
%type <integer> opt_mpls_stack_bottom
%type <integer> opt_icmp_mtu
%type <integer> gre_flags_list gre_flags gre_flag
%type <integer> gre_sum gre_off gre_key gre_seq
%type <integer> opt_icmp_echo_id
%type <integer> flow_label
%type <string> icmp_type opt_icmp_code flags
%type <string> opt_tcp_fast_open_cookie hex_blob
%type <string> opt_note note word_list
%type <string> option_flag option_value script
%type <string> opt_comma opt_equals
%type <window> opt_window
%type <urg_ptr> opt_urg_ptr
%type <sequence_number> opt_ack
%type <tcp_sequence_info> seq opt_icmp_echoed
%type <tcp_options> opt_tcp_options tcp_option_list
%type <tcp_option> tcp_option sack_block_list sack_block
%type <string> function_name
%type <expression_list> expression_list function_arguments
%type <expression> expression binary_expression array sub_expr_list
%type <expression> any_int decimal_integer hex_integer
%type <expression> inaddr in6addr sockaddr msghdr iovec pollfd opt_revents linger
%type <expression> opt_cmsg cmsg_expr
%type <expression> scm_timestamping_expr
%type <expression> sock_extended_err_expr
%type <expression> mpls_stack_expression
%type <expression> gre_header_expression
%type <expression> epollev
%type <errno_info> opt_errno
%type <port_info> opt_port_info

%%  /* The grammar follows. */

script
: opt_options opt_init_command events opt_cleanup_command {
	$$ = NULL;		/* The parser output is in out_script */
}
;

opt_options
:		{
	$$ = NULL;
	parse_and_finalize_config(invocation);
}
| options	{
	$$ = $1;
	parse_and_finalize_config(invocation);
}
;

options
: option		{
	out_script->option_list = $1;
	$$ = $1;		/* return the tail so we can append to it */
}
| options option	{
	$1->next = $2;
	$$ = $2;		/* return the tail so we can append to it */
}
;

option
: option_flag '=' option_value {
	$$ = new_option($1, $3);
}
| option_flag {
	$$ = new_option($1, NULL);
}

option_flag
: OPTION	{ $$ = $1; }
;

option_value
: INTEGER	{ $$ = strdup(yytext); }
| WORD		{ $$ = $1; }
| STRING	{ $$ = $1; }
| IPV4_ADDR	{ $$ = $1; }
| IPV6_ADDR	{ $$ = $1; }
| IPV4		{ $$ = strdup("ipv4"); }
| IPV6		{ $$ = strdup("ipv6"); }
| WORD '=' WORD {
	/* For consistency, allow syntax like: --define=PROTO=IPPROTO_TCP */
	char *lhs = $1, *rhs = $3;

	asprintf(&($$), "%s=%s", lhs, rhs);
	free(lhs);
	free(rhs);
}
| WORD '=' STRING {
	/* For consistency, allow syntax like: --define=CC="reno" */
	char *lhs = $1, *rhs = $3;

	asprintf(&($$), "%s=\"%s\"", lhs, rhs);
	free(lhs);
	free(rhs);
}
| WORD '=' BACK_QUOTED {
	/* For consistency, allow syntax like: --define=SCRIPT=`cleanup` */
	char *lhs = $1, *rhs = $3;

	asprintf(&($$), "%s=`%s`", lhs, rhs);
	free(lhs);
	free(rhs);
}
;

opt_init_command
:               { }
| init_command  { }
;

init_command
: command_spec  { out_script->init_command = $1; }
;

events
: event        {
	out_script->event_list = $1;  /* save pointer to event list as output
				       * of parser */
	$$ = $1;          /* return the tail so that we can append to it */
}
| events event {
	$1->next = $2;    /* link new event to the end of the existing list */
	$$ = $2;          /* return the tail so that we can append to it */
}
;

event
: event_time action  {
	$$ = $2;
	$$->line_number = $1->line_number;   /* use timestamp's line */
	$$->time_usecs  = $1->time_usecs;
	$$->time_usecs_end  = $1->time_usecs_end;
	$$->time_type = $1->time_type;

	if ($$->time_usecs_end != NO_TIME_RANGE) {
		if ($$->time_usecs_end < $$->time_usecs)
			semantic_error("time range is backwards");
	}
	if ($$->time_type == ANY_TIME &&  ($$->type != PACKET_EVENT ||
	    packet_direction($$->event.packet) != DIRECTION_OUTBOUND)) {
		yylineno = $$->line_number;
		semantic_error("event time <star> can only be used with "
			       "outbound packets");
	} else if (($$->time_type == ABSOLUTE_RANGE_TIME ||
		    $$->time_type == RELATIVE_RANGE_TIME) &&
	           ($$->type != PACKET_EVENT ||
		    packet_direction($$->event.packet) != DIRECTION_OUTBOUND)) {
		yylineno = $$->line_number;
		semantic_error("event time range can only be used with "
			       "outbound packets");
	}
	free($1);
}
;

event_time
: '+' time	{
	$$ = new_event(INVALID_EVENT);
	$$->line_number = @2.first_line;
	$$->time_usecs = $2;
	$$->time_type = RELATIVE_TIME;
}
| time         {
	$$ = new_event(INVALID_EVENT);
	$$->line_number = @1.first_line;
	$$->time_usecs = $1;
	$$->time_type = ABSOLUTE_TIME;
}
| '*'		{
	$$ = new_event(INVALID_EVENT);
	$$->line_number = @1.first_line;
	$$->time_type = ANY_TIME;
}
| time '~' time	{
	$$ = new_event(INVALID_EVENT);
	$$->line_number = @1.first_line;
	$$->time_type = ABSOLUTE_RANGE_TIME;
	$$->time_usecs = $1;
	$$->time_usecs_end = $3;
}
| '+' time '~' '+' time {
	$$ = new_event(INVALID_EVENT);
	$$->line_number = @1.first_line;
	$$->time_type = RELATIVE_RANGE_TIME;
	$$->time_usecs = $2;
	$$->time_usecs_end = $5;
}
;

time
: FLOAT        {
	if ($1 < 0) {
		semantic_error("negative time");
	}
	$$ = (s64)($1 * 1.0e6); /* convert float secs to s64 microseconds */
}
| INTEGER	{
	if ($1 < 0) {
		semantic_error("negative time");
	}
	$$ = (s64)($1 * 1000000); /* convert int secs to s64 microseconds */
}
;

action
: packet_spec  { $$ = new_event(PACKET_EVENT);  $$->event.packet  = $1; }
| syscall_spec { $$ = new_event(SYSCALL_EVENT); $$->event.syscall = $1; }
| command_spec { $$ = new_event(COMMAND_EVENT); $$->event.command = $1; }
| code_spec    { $$ = new_event(CODE_EVENT);    $$->event.code    = $1; }
;

packet_spec
: tcp_packet_spec  { $$ = $1; }
| udp_packet_spec  { $$ = $1; }
| icmp_packet_spec { $$ = $1; }
;

tcp_packet_spec
: packet_prefix opt_ip_info opt_port_info flags seq opt_ack opt_window opt_urg_ptr opt_tcp_options {
	char *error = NULL;
	struct packet *outer = $1, *inner = NULL;
	enum direction_t direction = outer->direction;

	if (($2.tos.check == TOS_CHECK_ECN) && ($2.tos.value == ECN_ECT01) &&
	    (direction != DIRECTION_OUTBOUND)) {
		semantic_error("[ect01] can only be used with outbound packets");
	}

	if (($9 == NULL) && (direction != DIRECTION_OUTBOUND)) {
		yylineno = @7.first_line;
		semantic_error("<...> for TCP options can only be used with "
			       "outbound packets");
	}

	inner = new_tcp_packet(in_config->wire_protocol,
			       direction, $2, $3.src_port, $3.dst_port, $4,
			       $5.start_sequence, $5.payload_bytes,
			       $6, $7, $8, $9, &error);
	free($4);
	free($9);
	if (inner == NULL) {
		assert(error != NULL);
		semantic_error(error);
		free(error);
	}

	$$ = packet_encapsulate_and_free(outer, inner);
}
;

udp_packet_spec
: packet_prefix opt_ip_info UDP opt_port_info '(' INTEGER ')' {
	char *error = NULL;
	struct packet *outer = $1, *inner = NULL;
	enum direction_t direction = outer->direction;

	if ($2.tos.check == TOS_CHECK_ECN) {
		semantic_error("ECN can only be used with TCP packets");
	}

	if (!is_valid_u16($6)) {
		semantic_error("UDP payload size out of range");
	}

	inner = new_udp_packet(in_config->wire_protocol, direction, $2,
			       $6, $4.src_port, $4.dst_port, &error);
	if (inner == NULL) {
		assert(error != NULL);
		semantic_error(error);
		free(error);
	}

	$$ = packet_encapsulate_and_free(outer, inner);
}
;

icmp_packet_spec
: packet_prefix opt_ip_info ICMP icmp_type opt_icmp_code opt_icmp_mtu
  opt_icmp_echo_id opt_icmp_echoed {
	char *error = NULL;
	struct packet *outer = $1, *inner = NULL;
	enum direction_t direction = outer->direction;

	if (($2.tos.check == TOS_CHECK_ECN) && ($2.tos.value == ECN_ECT01) &&
	    (direction != DIRECTION_OUTBOUND)) {
		semantic_error("[ect01] can only be used with outbound packets");
	}

	inner = new_icmp_packet(in_config->wire_protocol, direction, $4, $5,
				$8.protocol, $8.start_sequence,
				$8.payload_bytes, $2, $6, $7, &error);
	free($4);
	free($5);
	if (inner == NULL) {
		semantic_error(error);
		free(error);
	}

	$$ = packet_encapsulate_and_free(outer, inner);
}
;


packet_prefix
: direction {
	$$ = packet_new(PACKET_MAX_HEADER_BYTES);
	$$->direction = $1;
}
| packet_prefix IPV4 opt_ip_info IPV4_ADDR '>' IPV4_ADDR ':' {
	char *error = NULL;
	struct packet *packet = $1;
	u8 tos = $3.tos.value;
	u8 ttl = $3.ttl;
	char *ip_src = $4;
	char *ip_dst = $6;
	if (ipv4_header_append(packet, ip_src, ip_dst, tos, ttl, &error))
		semantic_error(error);
	free(ip_src);
	free(ip_dst);
	$$ = packet;
}
| packet_prefix IPV6 opt_ip_info IPV6_ADDR '>' IPV6_ADDR ':' {
	char *error = NULL;
	struct packet *packet = $1;
	u8 tos = $3.tos.value;
	u8 hop_limit = $3.ttl;
	char *ip_src = $4;
	char *ip_dst = $6;
	if (ipv6_header_append(packet, ip_src, ip_dst, tos, hop_limit, &error))
		semantic_error(error);
	free(ip_src);
	free(ip_dst);
	$$ = packet;
}
| packet_prefix GRE ':' {
	struct packet *packet = $1;
	struct expression *expr = new_expression(EXPR_GRE);
	$$ = append_gre(packet, expr);
}
| packet_prefix GRE opt_comma gre_header_expression ':' {
	struct packet *packet = $1;
	struct expression *expr = $4;
	$$ = append_gre(packet, expr);
}
| packet_prefix MPLS mpls_stack ':' {
	char *error = NULL;
	struct packet *packet = $1;
	struct mpls_stack *mpls_stack = $3;

	if (mpls_header_append(packet, mpls_stack, &error))
		semantic_error(error);
	free(mpls_stack);
	$$ = packet;
}
;

gre_header_expression
: gre_flags_list opt_comma
  gre_sum opt_comma
  gre_off opt_comma
  gre_key opt_comma
  gre_seq {
	$$ = new_expression(EXPR_GRE);
	$$->value.gre.flags = htons($1);
	$$->value.gre.be16[0] = htons($3);
	$$->value.gre.be16[1] = htons($5);
	$$->value.gre.be32[1] = htonl($7);
	$$->value.gre.be32[2] = htonl($9);
}
;

gre_flags_list
: FLAGS '[' gre_flags ']'	{ $$ = $3; }
| FLAGS any_int			{ $$ = $2->value.num; }
;

gre_flags
: gre_flag			{ $$ = $1; }
| gre_flag ',' gre_flags	{ $$ = $1 | $3; }
;

gre_flag
: NONE 				{ $$ = 0; }
| CHECKSUM PRESENT		{ $$ = GRE_FLAG_C; }
| KEY PRESENT			{ $$ = GRE_FLAG_K; }
| SEQUENCE PRESENT		{ $$ = GRE_FLAG_S; }
;

gre_sum
: SUM any_int			{ $$ = $2->value.num; }
;

gre_off
: OFF any_int			{ $$ = $2->value.num; }
;

gre_key
: KEY opt_equals any_int	{ $$ = $3->value.num; }
;

gre_seq
: SEQ any_int			{ $$ = $2->value.num; }
;

opt_comma
:				{ $$ = NULL; }
| ','				{ $$ = NULL; }
;

opt_equals
:				{ $$ = NULL; }
| '='				{ $$ = NULL; }
;

mpls_stack
:				{
	$$ = mpls_stack_new();
}
| mpls_stack mpls_stack_entry	{
	if (mpls_stack_append($1, $2))
		semantic_error("too many MPLS labels");
	$$ = $1;
}
;

mpls_stack_entry
:
'(' LABEL INTEGER ',' TC INTEGER ',' opt_mpls_stack_bottom TTL INTEGER ')' {
	char *error = NULL;
	s64 label = $3;
	s64 traffic_class = $6;
	bool is_stack_bottom = $8;
	s64 ttl = $10;
	struct mpls mpls;

	if (new_mpls_stack_entry(label, traffic_class, is_stack_bottom, ttl,
				 &mpls, &error))
		semantic_error(error);
	$$ = mpls;
}
;

opt_mpls_stack_bottom
:			{ $$ = 0; }
| '[' WORD ']' ','	{
	if (strcmp($2, "S") != 0)
		semantic_error("expected [S] for MPLS label stack bottom");
	free($2);
	$$ = 1;
}
;

icmp_type
: WORD		{ $$ = $1; }
;

opt_icmp_code
:		{ $$ = NULL; }
| WORD		{ $$ = $1; }
;

/* This specifies the relevant details about the packet echoed by ICMP. */
opt_icmp_echoed
:			{
	$$.start_sequence	= 0;
	$$.payload_bytes	= 0;
	$$.protocol		= IPPROTO_TCP;
}
| '[' UDP '(' INTEGER ')' ']'	{
	$$.start_sequence	= 0;
	$$.payload_bytes	= $4;
	$$.protocol		= IPPROTO_UDP;
}
| '[' seq ']'		{
	$$ = $2;
}
| '[' RAW '(' INTEGER ')' ']'   {
	$$.payload_bytes        = $4;
	$$.protocol             = IPPROTO_RAW;
}
;

opt_icmp_mtu
:		{ $$ = -1; }
| MTU INTEGER	{ $$ = $2; }
;

opt_icmp_echo_id
:                { $$ = 0; }
| ID INTEGER     { $$ = $2; }
;

opt_port_info
:		{
	$$.src_port		= 0;
	$$.dst_port		= 0;
}
| INTEGER '>' INTEGER	{
	if (!is_valid_u16($1)) {
		semantic_error("src port out of range");
	}
	if (!is_valid_u16($3)) {
		semantic_error("dst port out of range");
	}

	$$.src_port		= $1;
	$$.dst_port		= $3;
}
;

direction
: '<'          { $$ = DIRECTION_INBOUND;  current_script_line = yylineno; }
| '>'          { $$ = DIRECTION_OUTBOUND; current_script_line = yylineno; }
;

tos_spec
: ip_ecn		{ $$.check = TOS_CHECK_ECN; $$.value = $1; }
| TOS HEX_INTEGER	{
	s64 tos = $2;

	if (!is_valid_u8(tos)) {
		semantic_error("tos out of range for 8 bits");
	}

	$$.check = TOS_CHECK_TOS;
	$$.value = tos;
}
;

ip_ecn
: NO_ECN		{ $$ = ECN_NONE; }
| ECT0		{ $$ = ECN_ECT0; }
| ECT1		{ $$ = ECN_ECT1; }
| ECT01		{ $$ = ECN_ECT01; }
| CE		{ $$ = ECN_CE; }
;

flags
: WORD         { $$ = $1; }
| '.'          { $$ = strdup("."); }
| WORD '.'     { asprintf(&($$), "%s.", $1); free($1); }
| '-'          { $$ = strdup(""); }  /* no TCP flags set in segment */
;

flow_label
: FLOWLABEL HEX_INTEGER {
	s64 flowlabel = $2;

	if (!is_valid_u20(flowlabel)) {
		semantic_error("flowlabel out of range for 20 bits");
	}
	$$ = flowlabel;
}
;

ip_info
: tos_spec	{
	$$.tos.check = $1.check;
	$$.tos.value = $1.value;
	$$.flow_label = 0;
	$$.ttl = 0;
}
| flow_label	{
	$$.tos.check = TOS_CHECK_NONE;
	$$.tos.value = 0;
	$$.flow_label = $1;
	$$.ttl = 0;
}
| TTL INTEGER {
	$$.tos.check = TOS_CHECK_NONE;
	$$.tos.value = 0;
	$$.flow_label = 0;
	$$.ttl = $2;
}
| tos_spec ',' flow_label {
	$$.tos.check = $1.check;
	$$.tos.value = $1.value;
	$$.flow_label = $3;
	$$.ttl = 0;
}
;

opt_ip_info
:			{
	$$.tos.check = TOS_CHECK_NONE;
	$$.tos.value = 0;
	$$.flow_label = 0;
	$$.ttl = 0;
}
| '(' ip_info ')'	{ $$ = $2; }
| '[' ip_info ']'	{ $$ = $2; }
;

seq
: INTEGER ':' INTEGER '(' INTEGER ')' {
	if (!is_valid_u32($1)) {
		semantic_error("TCP start sequence number out of range");
	}
	if (!is_valid_u32($3)) {
		semantic_error("TCP end sequence number out of range");
	}
	if (!is_valid_u16($5)) {
		semantic_error("TCP payload size out of range");
	}
	if ($3 != ($1 +$5)) {
		semantic_error("inconsistent TCP sequence numbers and "
			       "payload size");
	}
	$$.start_sequence = $1;
	$$.payload_bytes = $5;
	$$.protocol = IPPROTO_TCP;
}
;

opt_ack
:              { $$ = 0; }
| ACK INTEGER  {
	if (!is_valid_u32($2)) {
		semantic_error("TCP ack sequence number out of range");
	}
	$$ = $2;
}
;

opt_window
:		{ $$ = -1; }
| WIN INTEGER	{
	if (!is_valid_u16($2)) {
		semantic_error("TCP window value out of range");
	}
	$$ = $2;
}
;

opt_urg_ptr
:		{ $$ = 0; }
| URG INTEGER	{
	if (!is_valid_u16($2)) {
		semantic_error("urg_ptr value out of range");
	}
	$$ = $2;
}
;

opt_tcp_options
:                             { $$ = tcp_options_new(); }
| '<' tcp_option_list '>'     { $$ = $2; }
| '<' ELLIPSIS '>'            { $$ = NULL; /* FLAG_OPTIONS_NOCHECK */ }
;

tcp_option_list
: tcp_option                       {
	$$ = tcp_options_new();
	if (tcp_options_append($$, $1)) {
		semantic_error("TCP option list too long");
	}
}
| tcp_option_list ',' tcp_option   {
	$$ = $1;
	if (tcp_options_append($$, $3)) {
		semantic_error("TCP option list too long");
	}
}
;

opt_tcp_fast_open_cookie
:			{ $$ = strdup(""); }
| hex_blob		{ $$ = $1; }
;

hex_blob
: WORD    { $$ = $1; }
| INTEGER { $$ = strdup(yytext); }
;

tcp_option
: NOP              { $$ = tcp_option_new(TCPOPT_NOP, 1); }
| EOL              { $$ = tcp_option_new(TCPOPT_EOL, 1); }
| MSS INTEGER      {
	$$ = tcp_option_new(TCPOPT_MAXSEG, TCPOLEN_MAXSEG);
	if (!is_valid_u16($2)) {
		semantic_error("mss value out of range");
	}
	$$->data.mss.bytes = htons($2);
}
| WSCALE INTEGER   {
	$$ = tcp_option_new(TCPOPT_WINDOW, TCPOLEN_WINDOW);
	if (!is_valid_u8($2)) {
		semantic_error("window scale shift count out of range");
	}
	$$->data.window_scale.shift_count = $2;
}
| SACKOK           {
	$$ = tcp_option_new(TCPOPT_SACK_PERMITTED,
				    TCPOLEN_SACK_PERMITTED);
}
| SACK sack_block_list {
	$$ = $2;
}
| MD5 hex_blob {
	char *error = NULL;
	$$ = new_md5_option($2, &error);
	free($2);
	if ($$ == NULL) {
		assert(error != NULL);
		semantic_error(error);
		free(error);
	}
}
| TIMESTAMP VAL INTEGER ECR INTEGER  {
	u32 val, ecr;
	$$ = tcp_option_new(TCPOPT_TIMESTAMP, TCPOLEN_TIMESTAMP);
	if (!is_valid_u32($3)) {
		semantic_error("ts val out of range");
	}
	if (!is_valid_u32($5)) {
		semantic_error("ecr val out of range");
	}
	val = $3;
	ecr = $5;
	$$->data.time_stamp.val = htonl(val);
	$$->data.time_stamp.ecr = htonl(ecr);
}
| FAST_OPEN opt_tcp_fast_open_cookie  {
	char *error = NULL;
	$$ = new_tcp_fast_open_option($2, &error, false);
	free($2);
	if ($$ == NULL) {
		assert(error != NULL);
		semantic_error(error);
		free(error);
	}
}
| FAST_OPEN_EXP opt_tcp_fast_open_cookie  {
	char *error = NULL;
	$$ = new_tcp_fast_open_option($2, &error, true);
	free($2);
	if ($$ == NULL) {
		assert(error != NULL);
		semantic_error(error);
		free(error);
	}
}
;

sack_block_list
: sack_block                 { $$ = $1; }
| sack_block_list sack_block {
	const int list_block_bytes =  $1->length - 2;
	assert(list_block_bytes > 0);
	assert((list_block_bytes % sizeof(struct sack_block)) == 0);
	const int num_blocks = list_block_bytes / sizeof(struct sack_block);
	/* Append this SACK block to the end of the array of blocks. */
	memcpy($1->data.sack.block + num_blocks, $2->data.sack.block,
		sizeof(struct sack_block));
	$1->length += sizeof(struct sack_block);
	free($2);
	$$ = $1;
}
;

sack_block
: INTEGER ':' INTEGER {
	$$ = tcp_option_new(TCPOPT_SACK, 2 + sizeof(struct sack_block));
	if (!is_valid_u32($1)) {
		semantic_error("TCP SACK left sequence number out of range");
	}
	if (!is_valid_u32($3)) {
		semantic_error("TCP SACK right sequence number out of range");
	}
	$$->data.sack.block[0].left = htonl($1);
	$$->data.sack.block[0].right = htonl($3);
}
;

syscall_spec
: opt_end_time function_name function_arguments '='
  expression opt_errno opt_note  {
	$$ = calloc(1, sizeof(struct syscall_spec));
	$$->end_usecs	= $1;
	$$->name	= $2;
	$$->arguments	= $3;
	$$->result	= $5;
	$$->error	= $6;
	$$->note	= $7;
}
;

opt_end_time
:                         { $$ = SYSCALL_NON_BLOCKING; }
| ELLIPSIS time           { $$ = $2; }
;

function_name
: WORD                    { $$ = $1; current_script_line = yylineno; }
;

function_arguments
: '(' ')'                 { $$ = NULL; }
| '(' expression_list ')' { $$ = $2; }
;

expression_list
: expression                     { $$ = new_expression_list($1); }
| expression_list ',' expression { $$ = $1; expression_list_append($1, $3); }
;

expression
: ELLIPSIS          {
	$$ = new_expression(EXPR_ELLIPSIS);
}
| any_int           { $$ = $1; }
| WORD              {
	$$ = new_expression(EXPR_WORD);
	$$->value.string = $1;
}
| STRING            {
	$$ = new_expression(EXPR_STRING);
	$$->value.string = $1;
	$$->format = "\"%s\"";
}
| STRING ELLIPSIS   {
	$$ = new_expression(EXPR_STRING);
	$$->value.string = $1;
	$$->format = "\"%s\"...";
}
| binary_expression {
	$$ = $1;
}
| array             {
	$$ = $1;
}
| inaddr          {
	$$ = $1;
}
| in6addr          {
	$$ = $1;
}
| sockaddr          {
	$$ = $1;
}
| msghdr            {
	$$ = $1;
}
| iovec             {
	$$ = $1;
}
| pollfd            {
	$$ = $1;
}
| linger            {
	$$ = $1;
}
| mpls_stack_expression {
	$$ = $1;
}
| cmsg_expr {
	$$ = $1;
}
| scm_timestamping_expr {
	$$ = $1;
}
| sub_expr_list {
	$$ = $1;
}
| sock_extended_err_expr {
	$$ = $1;
}
| '{' gre_header_expression '}' {
	$$ = $2;
}
| epollev            {
	$$ = $1;
}
;

any_int
: decimal_integer   { $$ = $1; }
| hex_integer       { $$ = $1; }
;

decimal_integer
: INTEGER           {
	$$ = new_integer_expression($1, "%ld");
}
;

hex_integer
: HEX_INTEGER       {
	$$ = new_integer_expression($1, "%#lx");
}
;

binary_expression
: expression '|' expression {       /* bitwise OR */
	$$ = new_expression(EXPR_BINARY);
	struct binary_expression *binary =
			  malloc(sizeof(struct binary_expression));
	binary->op = strdup("|");
	binary->lhs = $1;
	binary->rhs = $3;
	$$->value.binary = binary;
}
| WORD '=' expression {       /* symbol = value */
	$$ = new_expression(EXPR_BINARY);
	struct binary_expression *binary =
			  malloc(sizeof(struct binary_expression));
	binary->op = strdup("=");
	binary->lhs = new_expression(EXPR_WORD);
	binary->lhs->value.string = $1;
	binary->rhs = $3;
	$$->value.binary = binary;
}
;

array
: '[' ']'                 {
	$$ = new_expression(EXPR_LIST);
	$$->value.list = NULL;
}
| '[' expression_list ']' {
	$$ = new_expression(EXPR_LIST);
	$$->value.list = $2;
}
;

inaddr
: INET_ADDR '(' STRING ')' {
	__be32 ip_address = inet_addr($3);
	$$ = new_integer_expression(ip_address, "%#lx");
}
;

in6addr
: INET6_ADDR '(' STRING ')' {
	struct in6_addr ipv6_address;
	if (inet_pton(AF_INET6, $3, &ipv6_address) != 1) {
		semantic_error("cannot parse in6_addr");
	}
	$$ = new_expression(EXPR_IN6_ADDR);
	$$->value.address_ipv6 = ipv6_address;
}
;

sockaddr
:   '{' SA_FAMILY '=' WORD ','
	SIN_PORT '=' _HTONS_ '(' INTEGER ')' ','
	SIN_ADDR '=' INET_ADDR '(' STRING ')' '}' {
	if (strcmp($4, "AF_INET") == 0) {
		struct sockaddr_in *ipv4 = malloc(sizeof(struct sockaddr_in));
		memset(ipv4, 0, sizeof(*ipv4));
		ipv4->sin_family = AF_INET;
		ipv4->sin_port = htons($10);
		if (inet_pton(AF_INET, $17, &ipv4->sin_addr) == 1) {
			$$ = new_expression(EXPR_SOCKET_ADDRESS_IPV4);
			$$->value.socket_address_ipv4 = ipv4;
		} else {
			free(ipv4);
			semantic_error("invalid IPv4 address");
		}
	} else if (strcmp($4, "AF_INET6") == 0) {
		struct sockaddr_in6 *ipv6 = malloc(sizeof(struct sockaddr_in6));
		memset(ipv6, 0, sizeof(*ipv6));
		ipv6->sin6_family = AF_INET6;
		ipv6->sin6_port = htons($10);
		if (inet_pton(AF_INET6, $17, &ipv6->sin6_addr) == 1) {
			$$ = new_expression(EXPR_SOCKET_ADDRESS_IPV6);
			$$->value.socket_address_ipv6 = ipv6;
		} else {
			free(ipv6);
			semantic_error("invalid IPv6 address");
		}
	}
}
;

msghdr
: '{' MSG_NAME '(' ELLIPSIS ')' '=' ELLIPSIS ','
      MSG_IOV '(' decimal_integer ')' '=' array ','
      MSG_FLAGS '=' expression
      opt_cmsg '}' {
	struct msghdr_expr *msg_expr = calloc(1, sizeof(struct msghdr_expr));
	$$ = new_expression(EXPR_MSGHDR);
	$$->value.msghdr = msg_expr;
	msg_expr->msg_name	= new_expression(EXPR_ELLIPSIS);
	msg_expr->msg_namelen	= new_expression(EXPR_ELLIPSIS);
	msg_expr->msg_iov	= $14;
	msg_expr->msg_iovlen	= $11;
	msg_expr->msg_control	= $19;
	msg_expr->msg_flags	= $18;
}
;

opt_cmsg
:				{ $$ = new_expression(EXPR_LIST); }
| ',' MSG_CONTROL '=' array	{ $$ = $4; }
;

cmsg_expr
: '{' CMSG_LEVEL '=' expression ','
      CMSG_TYPE '=' expression ','
      CMSG_DATA '=' expression '}' {
	struct cmsg_expr *cmsg_expr = calloc(1, sizeof(struct cmsg_expr));
	$$ = new_expression(EXPR_CMSG);
	$$->value.cmsg = cmsg_expr;
	cmsg_expr->cmsg_level = $4;
	cmsg_expr->cmsg_type  = $8;
	cmsg_expr->cmsg_data  = $12;
}
;

scm_timestamping_expr
: '{' SCM_SEC '=' INTEGER ','
      SCM_NSEC '=' INTEGER '}' {
	struct scm_timestamping_expr *ts_expr =
		calloc(1, sizeof(struct scm_timestamping_expr));
	ts_expr->ts[0].tv_sec = $4;
	ts_expr->ts[0].tv_nsec = $8;

	$$ = new_expression(EXPR_SCM_TIMESTAMPING);
	$$->value.scm_timestamping = ts_expr;
}
;

sub_expr_list
: '{' expression_list '}' {
	$$ = new_expression(EXPR_LIST);
	$$->value.list = $2;
}
;

sock_extended_err_expr
: '{' EE_ERRNO '=' expression ','
      EE_ORIGIN '=' expression ','
      EE_TYPE '=' expression ','
      EE_CODE '=' expression ','
      EE_INFO '=' expression ','
      EE_DATA '=' expression '}' {
	struct sock_extended_err_expr *ee_expr =
		calloc(1, sizeof(struct sock_extended_err_expr));
	ee_expr->ee_errno = $4;
	ee_expr->ee_origin = $8;
	ee_expr->ee_type = $12;
	ee_expr->ee_code = $16;
	ee_expr->ee_info = $20;
	ee_expr->ee_data = $24;
	$$ = new_expression(EXPR_SOCK_EXTENDED_ERR);
	$$->value.sock_extended_err = ee_expr;
}
;

iovec
: '{' ELLIPSIS ',' decimal_integer '}' {
	struct iovec_expr *iov_expr = calloc(1, sizeof(struct iovec_expr));
	$$ = new_expression(EXPR_IOVEC);
	$$->value.iovec = iov_expr;
	iov_expr->iov_base = new_expression(EXPR_ELLIPSIS);
	iov_expr->iov_len = $4;
}
;

pollfd
: '{' FD '=' expression ',' EVENTS '=' expression opt_revents '}' {
	struct pollfd_expr *pollfd_expr = calloc(1, sizeof(struct pollfd_expr));
	$$ = new_expression(EXPR_POLLFD);
	$$->value.pollfd = pollfd_expr;
	pollfd_expr->fd = $4;
	pollfd_expr->events = $8;
	pollfd_expr->revents = $9;
}
;

epollev
: '{' EVENTS '=' expression ',' FD '=' expression '}' {
	struct epollev_expr *epollev_expr = calloc(1, sizeof(struct epollev_expr));
	$$ = new_expression(EXPR_EPOLLEV);
	$$->value.epollev = epollev_expr;
	epollev_expr->events = $4;
	epollev_expr->fd = $8;
} | '{' EVENTS '=' expression ',' PTR '=' expression '}' {
	struct epollev_expr *epollev_expr = calloc(1, sizeof(struct epollev_expr));
	$$ = new_expression(EXPR_EPOLLEV);
	$$->value.epollev = epollev_expr;
	epollev_expr->events = $4;
	epollev_expr->ptr = $8;
} | '{' EVENTS '=' expression ',' U32 '=' expression '}' {
	struct epollev_expr *epollev_expr = calloc(1, sizeof(struct epollev_expr));
	$$ = new_expression(EXPR_EPOLLEV);
	$$->value.epollev = epollev_expr;
	epollev_expr->events = $4;
	epollev_expr->u32 = $8;
} | '{' EVENTS '=' expression ',' U64 '=' expression '}' {
	struct epollev_expr *epollev_expr = calloc(1, sizeof(struct epollev_expr));
	$$ = new_expression(EXPR_EPOLLEV);
	$$->value.epollev = epollev_expr;
	epollev_expr->events = $4;
	epollev_expr->u64 = $8;
}
;

opt_revents
:                                { $$ = new_integer_expression(0, "%ld"); }
| ',' REVENTS '=' expression     { $$ = $4; }
;

linger
: '{' ONOFF '=' INTEGER ',' LINGER '=' INTEGER '}' {
	$$ = new_expression(EXPR_LINGER);
	$$->value.linger.l_onoff  = $4;
	$$->value.linger.l_linger = $8;
}
;

mpls_stack_expression
:
'{' mpls_stack '}'	{
	$$ = new_expression(EXPR_MPLS_STACK);
	$$->value.mpls_stack = $2;
}
;

opt_errno
:                   { $$ = NULL; }
| WORD note         {
	$$ = malloc(sizeof(struct errno_spec));
	$$->errno_macro = $1;
	$$->strerror    = $2;
}
;

opt_note
:                   { $$ = NULL; }
| note              { $$ = $1; }
;

note
: '(' word_list ')' { $$ = $2; }
;

word_list
: WORD              { $$ = $1; }
| FLAGS             { $$ = strdup("flags"); }
| word_list WORD    { asprintf(&($$), "%s %s", $1, $2); free($1); free($2); }
;

command_spec
: BACK_QUOTED       {
	$$ = malloc(sizeof(struct command_spec));
	$$->command_line = $1;
	current_script_line = yylineno;
}
;

code_spec
: CODE              {
	$$ = calloc(1, sizeof(struct code_spec));
	$$->text = $1;
	current_script_line = yylineno;
 }
;

opt_cleanup_command
:			{ }
| cleanup_command	{ }
;

cleanup_command
: command_spec {
	out_script->cleanup_command = $1;
	cleanup_cmd = out_script->cleanup_command->command_line;
}
;
